package com.quick.mybatis.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.quick.mybatis.annotation.ClassMetaEnum;
import com.quick.mybatis.annotation.Column;
import com.quick.mybatis.annotation.FieldMetaEnum;
import com.quick.mybatis.annotation.Id;
import com.quick.mybatis.annotation.OrderBy;
import com.quick.mybatis.annotation.Relevance;
import com.quick.mybatis.annotation.Table;


public final class ObjectUtil {

	private static final Map<String, Map<ClassMetaEnum, Object>> metaMap = new ConcurrentHashMap<String, Map<ClassMetaEnum, Object>>();
	private static final ReadWriteLock lock = new ReentrantReadWriteLock();
	private static final Lock read = lock.readLock();
	private static final Log log = LogFactory.getLog(ObjectUtil.class);

	@SuppressWarnings("unchecked")
	public static <T> T clone(Object obj) {
		String para = serializeByObjectMapper(obj);
		return (T) deSrializeByObjectMapper(para, obj.getClass());
	}

	@SuppressWarnings("unchecked")
	public static <T> T copy(Object obj) throws IOException, ClassNotFoundException {
		try {
			ByteArrayOutputStream byteout = new ByteArrayOutputStream();
			ObjectOutputStream out = new ObjectOutputStream(byteout);
			out.writeObject(obj);
			ByteArrayInputStream bytein = new ByteArrayInputStream(byteout.toByteArray());
			ObjectInputStream in = new ObjectInputStream(bytein);

			return (T) in.readObject();
		} catch (IOException e) {
			throw e;
		} catch (ClassNotFoundException e) {
			throw e;
		}
	}

	public static <T> T createNewInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException {
		try {
			T newObject = (T) (clazz.newInstance());
			return newObject;
		} catch (InstantiationException e) {
			throw e;
		} catch (IllegalAccessException e) {
			throw e;
		}
	}

	public static <T> T createNewInstance(Class<T> clazz, Class<?>[] paraClazzs, Object... paras) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException  {
		try {
			Constructor<T> cons = clazz.getConstructor(paraClazzs);
			T newObject = cons.newInstance(paras);
			return newObject;
		} catch (SecurityException e) {
			throw e;
		} catch (NoSuchMethodException e) {
			throw e;
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (InstantiationException e) {
			throw e;
		} catch (IllegalAccessException e) {
			throw e;
		} catch (InvocationTargetException e) {
			throw e;
		}
	}

	public static <T> T createNewInstance(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		@SuppressWarnings("unchecked")
		Class<T> cls = (Class<T>) ObjectUtil.getClass(className);
		return createNewInstance(cls);
	}

	public static <T> T createNewInstance(String className, Class<?>[] paraClazzs, Object... paras) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
		@SuppressWarnings("unchecked")
		Class<T> cls = (Class<T>) ObjectUtil.getClass(className);
		return createNewInstance(cls, paraClazzs, paras);
	}

	public static <T> T deSrializeByObjectMapper(String para, Class<T> clazz) {
		return JSON.parseObject(para, clazz);
	}

	public static boolean equals(Object obj1, Object obj2) {
		if (obj1 == null && obj2 == null) {
			return true;
		}
		if (obj1 == null || obj2 == null) {
			return false;
		}

		if (obj1.getClass().equals(obj2.getClass())) {
			return obj1.equals(obj2);
		}

		if (isNumber(obj1) && isNumber(obj2)) {
			return DoubleUtil.parse(obj1).equals(DoubleUtil.parse(obj2));
		}

		return false;
	}

	/**
	 * 格式化 代码和名称
	 * 
	 * @param code
	 * @param name
	 * @return
	 */
	public static String formatCodeName(String code, String name) {
		if (StringUtil.isEmpty(code)) {
			code = "";
		}

		if (StringUtil.isEmpty(name)) {
			name = "";
		}

		if (ObjectUtil.equals(code, name)) {
			return StringUtil.format("[{0}]", code);
		} else {
			return StringUtil.format("[{0}]{1}", code, name);
		}
	}

	public static Class<?> getClass(String className) throws ClassNotFoundException {
		Class<?> cls = classMaps.get(className);
		if (cls == null) {
			try {
				cls = Class.forName(className);
				classMaps.put(className, cls);
			} catch (ClassNotFoundException e) {
				throw e;
			}
		}
		return cls;
	}

	public static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
		return getField(clazz, fieldName, true);
	}

	public static Field getField(Class<?> clazz, String fieldName, Boolean throwError) throws NoSuchFieldException {
		try {
			return clazz.getField(fieldName);
		} catch (SecurityException e) {
			if (throwError) {
				throw e;
			}
			return null;
		} catch (NoSuchFieldException e) {
			if (throwError) {
				throw e;
			}
			return null;
		}

	}

	public static Field getField(String className, String fieldName) throws ClassNotFoundException, NoSuchFieldException {
		Class<?> cls = ObjectUtil.getClass(className);
		return getField(cls, fieldName, true);
	}

	public static Object getFieldValue(String className, String fieldName, Object obj) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
		Class<?> cls = getClass(className);
		Field field = getField(cls, fieldName);
		return getFieldValue(cls, field, obj);
	}

	public static Object getFieldValue(Class<?> clazz, String fieldName, Object obj) throws NoSuchFieldException, IllegalAccessException {
		Field field = getField(clazz, fieldName);
		return getFieldValue(clazz, field, obj);
	}

	public static Object getFieldValue(Class<?> clazz, Field field, Object obj) throws IllegalAccessException {
		try {
			return field.get(obj == null ? clazz : obj);
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (IllegalAccessException e) {
			throw e;
		}
	}

	public static Object getFieldValue(String className, String fieldName) throws ClassNotFoundException, NoSuchFieldException {
		Class<?> cls = ObjectUtil.getClass(className);
		return getField(cls, fieldName, true);
	}

	public static Method getMethod(Class<?> clazz, String methodName, Boolean throwError, Class<?>... paraClazzs) throws NoSuchMethodException {
		try {
			return clazz.getMethod(methodName, paraClazzs);
		} catch (SecurityException e) {
			if (throwError)
				throw e;
			else
				return null;
		} catch (NoSuchMethodException e) {
			if (throwError)
				throw e; 
			else
				return null;
		}
	}

	public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paraClazzs) throws NoSuchMethodException {
		return getMethod(clazz, methodName, true, paraClazzs);
	}

	public static Method getMethod(String className, String methodName, Class<?>... paraClazzs) throws NoSuchMethodException, ClassNotFoundException {
		Class<?> cls = ObjectUtil.getClass(className);
		return getMethod(cls, methodName, true, paraClazzs);
	}

	public static Object invokeMethod(Class<?> clazz, String methodName, Class<?>[] paraClazzs, Object obj,
			Object... paras) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		Method method = ObjectUtil.getMethod(clazz, methodName, paraClazzs);
		if(method == null){
			log.error(methodName +" is null.");
			return null;
		}
		return invokeMethod(method, obj, paras);
	}

	public static Object invokeMethod(Method method, Object obj, Object... paras) throws IllegalAccessException, InvocationTargetException {
		try {
			return method.invoke(obj, paras);
		} catch (IllegalArgumentException e) {
			log.error(e);
			throw e;
		} catch (IllegalAccessException e) {
			log.error(e);
			throw e;
		} catch (InvocationTargetException e) {
			log.error(e);
			throw e;
		}
	}

	public static Object invokeMethod(String className, String methodName, Class<?>[] paraClazzs, Object obj,
			Object... paras) throws NoSuchMethodException, ClassNotFoundException, IllegalAccessException, InvocationTargetException {
		Method method = ObjectUtil.getMethod(className, methodName, paraClazzs);
		return invokeMethod(method, obj, paras);
	}

	public static String serializeByObjectMapper(Object obj) {
		return JSON.toJSONString(obj);
	}

	private static boolean isNumber(Object obj) {
		Class<? extends Object> clazz = obj.getClass();
		return clazz.equals(int.class) || clazz.equals(Integer.class) || clazz.equals(long.class)
				|| clazz.equals(Long.class) || clazz.equals(float.class) || clazz.equals(Float.class)
				|| clazz.equals(double.class) || clazz.equals(Double.class);
	}

	private static Map<String, Class<?>> classMaps = new ConcurrentHashMap<String, Class<?>>();

	public static <T> Map<ClassMetaEnum, Object> getClassMap(Class<T> entity) {
		read.lock();
		if (metaMap.get(entity.getCanonicalName()) != null) {
			return metaMap.get(entity.getCanonicalName());
		}
		final Map<String, Map<FieldMetaEnum, Object>> fieldList = new HashMap<String, Map<FieldMetaEnum, Object>>();
		final Map<String, Map<FieldMetaEnum, Object>> methodList = new HashMap<String, Map<FieldMetaEnum, Object>>();
		final Map<String, Map<FieldMetaEnum, Object>> relevanceMetaList = new HashMap<String, Map<FieldMetaEnum, Object>>();
		final Map<String, Map<FieldMetaEnum, Object>> orderByMetaList = new HashMap<String, Map<FieldMetaEnum, Object>>();
		final Map<ClassMetaEnum, Object> classMap = new HashMap<ClassMetaEnum, Object>();
		try {
			if (entity != null) {
				Table tableMeta = entity.getAnnotation(Table.class);
				classMap.put(ClassMetaEnum.C_TABLE_META, tableMeta);
				classMap.put(ClassMetaEnum.C_CLASS, entity.getName());
				setField(entity, fieldList);
				setMethod(entity, methodList);
				setRelevance(entity, relevanceMetaList);
				setOrderBy(entity, orderByMetaList);
				classMap.put(ClassMetaEnum.C_FIELD, fieldList);
				classMap.put(ClassMetaEnum.C_METHOD, methodList);
				classMap.put(ClassMetaEnum.C_RELEVANCE, relevanceMetaList);
				classMap.put(ClassMetaEnum.C_ORDER, orderByMetaList);
				metaMap.put(entity.getCanonicalName(), classMap);
			}
		} finally {
			read.unlock();
		}
		return classMap;
	}
	
	public static <T> Map<ClassMetaEnum, Object> getClassMaps(Class<T> entity) {
        read.lock();
        if (metaMap.get(entity.getCanonicalName()) != null) {
            return metaMap.get(entity.getCanonicalName());
        }
        final Map<String, Map<FieldMetaEnum, Object>> fieldList = new HashMap<String, Map<FieldMetaEnum, Object>>();
        final Map<ClassMetaEnum, Object> classMap = new HashMap<ClassMetaEnum, Object>();
        try {
            if (entity != null) {
                Table tableMeta = entity.getAnnotation(Table.class);
                classMap.put(ClassMetaEnum.C_TABLE_META, tableMeta);
                classMap.put(ClassMetaEnum.C_CLASS, entity.getName());
                setField2(entity, fieldList);
                classMap.put(ClassMetaEnum.C_FIELD, fieldList);
                metaMap.put(entity.getCanonicalName(), classMap);
            }
        } finally {
            read.unlock();
        }
        return classMap;
    }
	@SuppressWarnings("unchecked")
	private static <T> void setField2(Class<T> entity, Map<String, Map<FieldMetaEnum, Object>> fieldList) {
        /**
         * 返回类中所有字段，包括公共、保护、默认（包）访问和私有字段，但不包括继承的字段
         * entity.getFields();只返回对象所表示的类或接口的所有可访问公共字段
         * 在class中getDeclared**()方法返回的都是所有访问权限的字段、方法等； 可看API
         */
        Field[] fields = entity.getDeclaredFields();
        if(fields == null || fields.length <=0){
            return;
        }
        Map<FieldMetaEnum, Object> fieldMap = null;
        boolean flag = false;
        for (Field f : fields) {
            fieldMap = new HashMap<FieldMetaEnum, Object>();
            flag = false;
            Relevance relevanceMeta = f.getAnnotation(Relevance.class);
            OrderBy orderByMeta = f.getAnnotation(OrderBy.class);
            Column columnMeta = f.getAnnotation(Column.class);
            Id id = f.getAnnotation(Id.class);
            if (columnMeta != null) {
                flag = true;
                fieldMap.put(FieldMetaEnum.COLUMN, columnMeta);
            }
            if(relevanceMeta != null){
                flag = true;
                fieldMap.put(FieldMetaEnum.RELEVANCE, relevanceMeta);
            }
            if(orderByMeta != null){
                flag = true;
                fieldMap.put(FieldMetaEnum.ORDER, orderByMeta);
            }
            if(id != null){
                flag = true;
                fieldMap.put(FieldMetaEnum.ID, id);
            }
            if(flag){
                fieldMap.put(FieldMetaEnum.FIELD, f);
                fieldList.put(f.getName(), fieldMap);
            }
        }
        Class superclass = entity.getSuperclass();   
        if (superclass != null && !superclass.getName().equals("java.lang.Object")) {// 简单的递归一下   
            setField2(superclass,  fieldList);   
        }   
    }

	private static <T> void setField(Class<T> entity, Map<String, Map<FieldMetaEnum, Object>> fieldList) {
		/**
		 * 返回类中所有字段，包括公共、保护、默认（包）访问和私有字段，但不包括继承的字段
		 * entity.getFields();只返回对象所表示的类或接口的所有可访问公共字段
		 * 在class中getDeclared**()方法返回的都是所有访问权限的字段、方法等； 可看API
		 */
		Field[] fields = entity.getDeclaredFields();
		//
		Map<FieldMetaEnum, Object> fieldMap = null;
		for (Field f : fields) {
			// 获取字段中包含fieldMeta的注解
		    Column meta = f.getAnnotation(Column.class);
			if (meta != null) {
				fieldMap = new HashMap<FieldMetaEnum, Object>();
				fieldMap.put(FieldMetaEnum.META, meta);
				fieldMap.put(FieldMetaEnum.FIELD, f);
				fieldList.put(f.getName(), fieldMap);
			}
		}
	}

	private static <T> void setRelevance(Class<T> entity, Map<String, Map<FieldMetaEnum, Object>> relevanceMetaList) {
		// 返回对象所表示的类或接口的所有可访问公共方法
		Method[] methods = entity.getMethods();
		Map<FieldMetaEnum, Object> methodMap = null;
		for (Method m : methods) {
			Relevance meta = m.getAnnotation(Relevance.class);
			if (meta != null) {
				methodMap = new HashMap<FieldMetaEnum, Object>();
				String name = StringUtil.getFirstLower(removeGet(m.getName()));
				methodMap.put(FieldMetaEnum.META, meta);
				methodMap.put(FieldMetaEnum.NAME, name);
				methodMap.put(FieldMetaEnum.TABLE_NAME, meta.tableName());
				if(StringUtil.isNotEmpty(meta.fieldName())){
					methodMap.put(FieldMetaEnum.FIELD, meta.fieldName());
				}else{
					methodMap.put(FieldMetaEnum.FIELD, name);
				}
				if(StringUtil.isNotEmpty(meta.columnName())){
					methodMap.put(FieldMetaEnum.COLUMN, meta.columnName());
				}else{
					methodMap.put(FieldMetaEnum.COLUMN, name);
				}
				methodMap.put(FieldMetaEnum.TYPE, meta.innerType());
				relevanceMetaList.put(m.getName(), methodMap);
			}
		}

	}
	
	private static <T> void setOrderBy(Class<T> entity, Map<String, Map<FieldMetaEnum, Object>> orderByMetaList) {
		// 返回对象所表示的类或接口的所有可访问公共方法
		Method[] methods = entity.getMethods();
		Map<FieldMetaEnum, Object> methodMap = null;
		Map<Integer,Map<FieldMetaEnum, Object>> tempMap = new LinkedHashMap<Integer, Map<FieldMetaEnum,Object>>(); 
		for (Method m : methods) {
			OrderBy meta = m.getAnnotation(OrderBy.class);
			if (meta != null) {
				methodMap = new LinkedHashMap<FieldMetaEnum, Object>();
				String name = StringUtil.getFirstLower(removeGet(m.getName()));
				methodMap.put(FieldMetaEnum.META, meta);
				methodMap.put(FieldMetaEnum.NAME, name);
				methodMap.put(FieldMetaEnum.METHOD, m.getName());
				methodMap.put(FieldMetaEnum.ORDERBYTYPE, meta.orderType());
				methodMap.put(FieldMetaEnum.ORDER, meta.order());
				if(StringUtil.isNotEmpty(meta.columnName())){
					methodMap.put(FieldMetaEnum.COLUMN, meta.columnName());
				}else{
					methodMap.put(FieldMetaEnum.COLUMN, name);
				}
				tempMap.put(meta.order(), methodMap);
			}
		}
		
		List<Map.Entry<Integer, Map<FieldMetaEnum, Object>>> list =
			    new ArrayList<Map.Entry<Integer, Map<FieldMetaEnum, Object>>>(tempMap.entrySet());
		orderBy(list);
		for (int i = 0; i < list.size(); i++) {
			Map.Entry<Integer, Map<FieldMetaEnum, Object>> map = list.get(i);
			Map<FieldMetaEnum, Object> value = map.getValue();
		    orderByMetaList.put(String.valueOf(value.get(FieldMetaEnum.METHOD)), value);
		}
	}
	
	private static void orderBy(List<Map.Entry<Integer, Map<FieldMetaEnum, Object>>> infoIds){
		Collections.sort(infoIds, new Comparator<Map.Entry<Integer, Map<FieldMetaEnum, Object>>>() {   
		    public int compare(Map.Entry<Integer, Map<FieldMetaEnum, Object>> o1, Map.Entry<Integer, Map<FieldMetaEnum, Object>> o2) {      
		        //return (o2.getValue() - o1.getValue()); 
		        return (o1.getKey()).compareTo(o2.getKey());
		    }
		}); 
	}

	private static <T> void setMethod(Class<T> entity, Map<String, Map<FieldMetaEnum, Object>> methodList) {
		/**
		 * 返回类中所有字段，包括公共、保护、默认（包）访问和私有字段，但不包括继承的字段
		 * entity.getFields();只返回对象所表示的类或接口的所有可访问公共字段
		 * 在class中getDeclared**()方法返回的都是所有访问权限的字段、方法等； 可看API
		 */
		// 返回对象所表示的类或接口的所有可访问公共方法
		Method[] methods = entity.getMethods();
		Map<FieldMetaEnum, Object> methodMap = null;
		for (Method m : methods) {
		    Column meta = m.getAnnotation(Column.class);
			if (meta != null) {
				methodMap = new HashMap<FieldMetaEnum, Object>();
				methodMap.put(FieldMetaEnum.META, meta);
				methodMap.put(FieldMetaEnum.NAME, StringUtil.getFirstLower(removeGet(m.getName())));
				if (StringUtil.isEmpty(meta.javaType())) {
					methodMap.put(FieldMetaEnum.TYPE, m.getReturnType());
				} else {
					methodMap.put(FieldMetaEnum.TYPE, meta.javaType());
				}

				methodList.put(m.getName(), methodMap);
			}
		}

	}

	private static String removeGet(String name) {
		return name.substring(3, name.length());
	}

	public static <T> Object getPrimaryKeyValue(Class<T> t, Object obj) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		Object value = ObjectUtil.invokeMethod(t, getGetMethod(t, getPrimaryKey(t)), null, obj);
		return value;
	}

	public static <T> Object getFieldValue(Class<T> t, Object obj, String field) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		Object value = ObjectUtil.invokeMethod(t, getGetMethod(t, field), null, obj);
		return value;
	}
	@SuppressWarnings("unchecked")
	public static <T> String getPrimaryKey(Class<T> t) {
		Map<ClassMetaEnum, Object> classMap = getClassMaps(t);
		Map<String, Map<FieldMetaEnum, Object>> methodMap = (Map<String, Map<FieldMetaEnum, Object>>) classMap
                .get(ClassMetaEnum.C_FIELD);
		for (Entry<String, Map<FieldMetaEnum, Object>> entry : methodMap.entrySet()) {
		    Map<FieldMetaEnum, Object> meta = entry.getValue();
            Column fieldMeta = (Column) meta.get(FieldMetaEnum.COLUMN);
			if (fieldMeta != null && fieldMeta.primaryKey()) {
				return entry.getKey();
			}
		}
		throw new NullPointerException(" primaryKey is null");
	}

	public static <T> String getSetMethod(Class<T> t, String field) {
		String method = "set" + StringUtil.getFirstUpper(field);
		return method;
	}

	public static <T> String getGetMethod(Class<T> t, String field) {
		String method = "get" + StringUtil.getFirstUpper(field);
		return method;
	}

	public static <T> T mapConvetObject(Map<String, Object> map, Class<T> t) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
		T obj = createNewInstance(t);
		if(map == null || map.size() <=0){
		    return obj;
		}
		JSONObject jsonObject = new JSONObject();
		for (Entry<String, Object> entry : map.entrySet()) {
			Object value = entry.getValue();
			String fieldName=StringUtil.getColumnName(entry.getKey());
			jsonObject.put(fieldName, value);
		}
		try {
			T o=JSONObject.parseObject(jsonObject.toString(), t);
			return o;
		} catch (Exception e) {
			log.info("对象转换异常:"+e.getMessage()+",");
		}
		return null;
	}
	
	public static <T> List<T> mapConvetObject(List<Map<String, Object>> listMap, Class<T> t) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
		if (listMap == null || listMap.size() <= 0) {
			return new ArrayList<T>();
		}
		List<T> list = new ArrayList<T>();
		for (Map<String, Object> value : listMap) {
			list.add(mapConvetObject(value, t));
		}
		return list;
	}
}
